package im.composer.media.sound.codec;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Paths;

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.sound.sampled.spi.AudioFileReader;

import org.tritonus.share.sampled.AudioFileTypes;

import im.composer.media.audio.stream.LinkedByteArrayInputStream;
import javazoom.jl.decoder.Bitstream;
import javazoom.jl.decoder.BitstreamException;
import javazoom.jl.decoder.Decoder;
import javazoom.jl.decoder.Header;
import javazoom.jl.decoder.SampleBuffer;

public class LameFileReader extends AudioFileReader {

	private static final ByteOrder BYTEORDER = ByteOrder.BIG_ENDIAN;

	private AudioFileFormat getAudioFileFormat(InputStream stream, long len) throws UnsupportedAudioFileException, IOException {
		Bitstream bitstream = new Bitstream(stream);
		Header h;
		try {
			h = bitstream.readFrame();
		} catch (BitstreamException e) {
			throw new UnsupportedAudioFileException(e.getLocalizedMessage());
		}
		int channels = h.mode() == Header.SINGLE_CHANNEL ? 1 : 2;
		AudioFormat format = new AudioFormat(h.frequency(), 16, channels, true, BYTEORDER == ByteOrder.BIG_ENDIAN);
		return new AudioFileFormat(new AudioFileTypes("LAME", "mp3"), format, (int) (h.total_ms(len)/1000f*h.frequency()));
	}

	@Override
	public AudioFileFormat getAudioFileFormat(InputStream stream) throws UnsupportedAudioFileException, IOException {
		return getAudioFileFormat(stream, -1);
	}

	@Override
	public AudioFileFormat getAudioFileFormat(URL url) throws UnsupportedAudioFileException, IOException {
		long len = -1;
		try {
			len = Files.size(Paths.get(url.toURI()));
		} catch (Exception e) {
		}
		InputStream in;
		try {
			in = Files.newInputStream(Paths.get(url.toURI()));
		} catch (Exception e) {
			in = url.openStream();
		}
		return getAudioFileFormat(in,len);
	}

	@Override
	public AudioFileFormat getAudioFileFormat(File file) throws UnsupportedAudioFileException, IOException {
		return getAudioFileFormat(new FileInputStream(file), file.length());
	}

	private AudioInputStream getAudioInputStream(InputStream stream,long len) throws UnsupportedAudioFileException, IOException {
		Bitstream bitstream = new Bitstream(stream);
		Decoder decoder = new Decoder();
		final Header[] first_frame = new Header[1];
		try {
			first_frame[0] = bitstream.readFrame();
		} catch (BitstreamException e) {
			throw new UnsupportedAudioFileException(e.getLocalizedMessage());
		}
		int channels = first_frame[0].mode() == Header.SINGLE_CHANNEL ? 1 : 2;
		int srate = first_frame[0].frequency();
		AudioFormat format = new AudioFormat(srate, 16, channels, true, BYTEORDER == ByteOrder.BIG_ENDIAN);
		InputStream lbais = new LinkedByteArrayInputStream() {
			Header h = first_frame[0];

			@Override
			public int available() throws IOException {
				if (super.available() == 0) {
					try {
						while (true) {
							if (h == null) {
								h = bitstream.readFrame();
							}
							SampleBuffer output = (SampleBuffer) decoder.decodeFrame(h, bitstream);
							short[] s_buf = output.getBuffer();
							bitstream.closeFrame();
							h = null;
							if (output.getBufferLength() < 1) {
								continue;
							}
							ByteBuffer bb = ByteBuffer.allocate(output.getBufferLength() * 2).order(BYTEORDER);
							bb.asShortBuffer().put(s_buf, 0, output.getBufferLength());
							byte[] b = bb.array();
							offer(b);
							break;
						}
					} catch (Exception e) {
						close();
					}
				}
				return super.available();
			}

		};
		Header h = first_frame[0];
		return new AudioInputStream(lbais, format,  (int) (h.total_ms(len)/1000f*h.frequency()));
	}
	@Override
	public AudioInputStream getAudioInputStream(InputStream stream) throws UnsupportedAudioFileException, IOException {
		return getAudioInputStream(stream,-1);
	}

	@Override
	public AudioInputStream getAudioInputStream(URL url) throws UnsupportedAudioFileException, IOException {
		long len = -1;
		try {
			len = Files.size(Paths.get(url.toURI()));
		} catch (Exception e) {
		}
		InputStream in;
		try {
			in = Files.newInputStream(Paths.get(url.toURI()));
		} catch (Exception e) {
			in = url.openStream();
		}
		return getAudioInputStream(in,len);
	}

	@Override
	public AudioInputStream getAudioInputStream(File file) throws UnsupportedAudioFileException, IOException {
		return getAudioInputStream(new FileInputStream(file), file.length());
	}

}
